/*

This file is part of roomyak.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
roomyak is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
roomyak is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with roomyak; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/
package com.datasentinel.os;

import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Logger;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import com.datasentinel.os.mac.MacOSToolKit;
import com.datasentinel.os.unix.AbstractUNIXToolKit;
import com.datasentinel.os.unix.ConsoleUnixToolKit;
import com.datasentinel.os.unix.gui.FreeDesktopDotOrgOSToolKit;
import com.datasentinel.os.unix.gui.GnomeOSToolKit;
import com.datasentinel.os.unix.gui.KDEOSToolKit;
import com.datasentinel.os.windows.WindowsOSToolKit;

public abstract class OSToolKit
{
	private static Logger logger = Logger.getLogger(OSToolKit.class.getName());
	private static OSToolKit toolKit;
	protected static String os;
	private static String desktop;
	protected static Icon ICON_FILE;
	
	static
	{
		try
		{
			String resourcesFolder = OSToolKit.class.getPackage().getName().replace('.', '/') + "/resources";
			ICON_FILE = new ImageIcon(OSToolKit.class.getClassLoader().getResource(resourcesFolder + "/file.gif"));
		}
		catch (Exception e)
		{
			System.out.println("Can't find: '" + OSToolKit.class.getPackage().getName().replace('.', '/') + "/resources'");
			e.printStackTrace();
		}
	}
	
	private String[] macAddresses;

	public synchronized static OSToolKit getInstance()
	{
		if (toolKit == null)
		{
			os = System.getProperty("os.name", "unknown").toLowerCase();
			desktop = System.getProperty("sun.desktop", "unknown").toLowerCase();
			switch (File.separatorChar)
			{
				case '\\':
					if (os.indexOf("windows") > -1)
						toolKit = new WindowsOSToolKit();
					break;
				case '/':
					if (os.indexOf("mac") > -1)
						toolKit = new MacOSToolKit();
					else if (isFreeDesktopDotOrgSupported())
					{
						toolKit = new FreeDesktopDotOrgOSToolKit();
					}
					else if (isGNOMERunning() && isGNOMESupported())
					{
						toolKit = new GnomeOSToolKit();
					}
					else if (isKDERunning() && isKDESupported())
					{
						toolKit = new KDEOSToolKit();
					}
					else
					{
						toolKit = new ConsoleUnixToolKit();
					}
					break;
			}
		}
		if (toolKit == null)
		{
			String msg = String.format("Could not load instance of OSToolKit "
					+ "for os.name=[%s], sun.desktop=[%s], " + "and File.separatorChar=[%c]", os,
					desktop, File.separatorChar);
			logger.severe(msg);
			throw new RuntimeException(msg);
		}
		return toolKit;
	}

	private static boolean isFreeDesktopDotOrgSupported()
	{
		return isExecutableFilePresent(new String[] { "xdg-open", "--version" });
	}

	private static boolean isExecutableFilePresent(final String[] command)
	{
		if (command == null || command.length < 1)
			return false;
		logger.info("Trying to run [" + command[0] + "]");
		final Thread waiter = new Thread();
		class ProcessVerifier extends Thread
		{
			private boolean processNormalTermination;
			private Process process;

			public ProcessVerifier()
			{
				processNormalTermination = false;
			}

			@Override
			public void run()
			{
				try
				{
					logger.info("Starting [" + command[0] + "]");
					process = Runtime.getRuntime().exec(command);
					logger.info("Started [" + command[0] + "]");
					processNormalTermination = process.waitFor() == 0;
				}
				catch (Exception e)
				{
					// Not a problem, the executable must be absent
					logger.info(e.getMessage());
				}
				synchronized (waiter)
				{
					waiter.notify();
				}
			}

			public void stopProcessIfNeeded()
			{
				try
				{
					interrupt();
					if (process == null)
						return;
					process.exitValue();
				}
				catch (Exception e)
				{
					if (process != null)
						process.destroy();
				}
			}

			public boolean isProcessNormalTermination()
			{
				return processNormalTermination;
			}
		}
		ProcessVerifier process = new ProcessVerifier();
		process.start();
		try
		{
			synchronized (waiter)
			{
				if (!process.isProcessNormalTermination())
					waiter.wait(1000);
			}
			process.stopProcessIfNeeded();
		}
		catch (InterruptedException e)
		{
		}
		return process.isProcessNormalTermination();
	}

	private static boolean isKDERunning()
	{
		if (System.getenv("KDE_SESSION_UID") != null || System.getenv("KDE_FULL_SESSION") != null)
			return true;
		return false;
	}

	private static boolean isGNOMERunning()
	{
		if (System.getenv("GNOME_SESSION_ID") != null
				|| System.getenv("GNOME_DESKTOP_SESSION_ID") != null)
			return true;
		return false;
	}

	private static boolean isGNOMESupported()
	{
		return isExecutableFilePresent(new String[] { "gnome-open", "--version" });
	}

	private static boolean isKDESupported()
	{
		return isExecutableFilePresent(new String[] { "kfmclient", "--version" });
	}

	public String[] getMacAddresses() throws IOException
	{
		if (macAddresses == null)
		{
			ArrayList<String> macs = getMacAddresses(new ArrayList<String>());
			macAddresses = macs.toArray(new String[macs.size()]);
		}
//		 macAddresses = new String[]{"00-17-3F-B1-EB-30"};//Loren
//		 macAddresses = new String[]{"00-1F-3A-07-E9-A9"};
//		 macAddresses = new String[]{"00-1B-77-2E-FF-00"};//Rick
		return macAddresses;
	}

	public String getOsName()
	{
		return os;
	}

	public boolean isWindows()
	{
		return false;
	}

	protected URI toURI(File dir, String command) throws URISyntaxException, MalformedURLException
	{
		if (command.indexOf("://") > 0)
			return new URL(command).toURI();
		return new File(dir, command).toURI();
	}

	public boolean isCapsLockEnabled()
	{
		boolean isCapsLockEnabled = false;
		try
		{
			return Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
		}
		catch (UnsupportedOperationException use)
		{
			logger
					.warning("Toolkit.getLockingKeyState(KeyEvent.VK_CAPS_LOCK) is not supported on this platform");
		}
		return isCapsLockEnabled;
	}

	abstract public ArrayList<String> getMacAddresses(ArrayList<String> macs) throws IOException;

	abstract public String getTempDirDefault();

	abstract public void makeLink(File link, File file) throws IOException;

	abstract public boolean isProcessRunning(String processName, boolean killProcess)
			throws IOException;

	abstract public long getProcessId() throws IOException;

	abstract public long getProcessId(String processSpecifier) throws IOException;

	abstract public boolean killProcess(long processId) throws IOException;

	abstract public ProcessIterator exec(File dir, String command, String args) throws Exception;

	public ProcessIterator exec(String commands[]) throws IOException
	{
		return exec(commands, null);
	}

	public ProcessIterator exec(String commands[], File workingDirectory) throws IOException
	{
		ProcessBuilder processBuilder = new ProcessBuilder(commands);
		if (workingDirectory != null)
			processBuilder.directory(workingDirectory);
		processBuilder.redirectErrorStream(true);
		return new ProcessIterator(processBuilder.start());
	}

	public void execAndForget(String command) throws Exception
	{
		execAndForget(null, command);
	}

	public void execAndForget(File workingDirectory, String command) throws Exception
	{
		execAndForget(workingDirectory, command, null);
	}

	public void execAndForget(File workingDirectory, String command, String[] arguments)
			throws Exception
	{
		/**
		 * @TODO Revise this part, if multiple arguments are specified might not
		 *       work properly.
		 */
		String[] processBuilderArguments = null;
		int argumentSize = null == arguments?0:arguments.length;
		int argumentOffset = 1;
		if (toolKit instanceof AbstractUNIXToolKit)
		{
			argumentOffset = 2;
			processBuilderArguments = new String[argumentOffset + argumentSize];
			processBuilderArguments[0] = ((AbstractUNIXToolKit)toolKit).getOpenFileCommand();
			processBuilderArguments[1] = command;
		}
		else
		{
			processBuilderArguments = new String[argumentOffset + argumentSize];
			processBuilderArguments[0] = command;
		}
		
		if(argumentSize > 0)
		System.arraycopy(arguments, 0, processBuilderArguments, argumentOffset, argumentSize);
		
		ProcessBuilder processBuilder = new ProcessBuilder(processBuilderArguments);
		if (workingDirectory != null)
			processBuilder.directory(workingDirectory);
		processBuilder.redirectErrorStream(true);
		Process p = processBuilder.start(); // Runtime.getRuntime().exec(command,
		// null, workingDirectory);
		InputStream processInputStream = p.getInputStream();
		InputStream processErrorStream = p.getErrorStream();
		processInputStream.close();
		processErrorStream.close();
	}

	public Icon getFileIcon(String fileName)
	{
		return ICON_FILE;
	}
	
	public boolean setProcessPriorityBelowNormal()
	{
		return false;
	}

	public boolean setProcessPriorityNormal()
	{
		return false;
	}
}
